﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Xml.XPath;

namespace WinSCP.API
{
    public class LogReader
    {
        private static int BUFFER_SIZE = 1024;

        public LogReader(string logPath)
        {
            this._logPath = logPath;

            if (File.Exists(logPath))
                Update();
        }

        public void Update()
        {
            if (!File.Exists(this._logPath))
                return;

            FileStream logFs = new FileStream(this._logPath,
                FileMode.Open,
                FileAccess.Read,
                FileShare.ReadWrite,
                BUFFER_SIZE,
                FileOptions.Asynchronous);

            StreamReader logRdr = new StreamReader(logFs);
            string logText = logRdr.ReadToEnd();

            byte[] rawData = Encoding.UTF8.GetBytes(
                logText.Contains("</session>") ?
                logText : logText + "</session>");

            MemoryStream ms = new MemoryStream(rawData);

            // This is the only/best way I could think of to determine whether
            // or not WinSCP is done writing to the log.
            XPathDocument xDoc = null;
            try
            {
                xDoc = new XPathDocument(ms);
            }
            catch (XmlException)
            {
                return;
            }

            XPathNodeIterator iter;
            XPathNavigator xNav = xDoc.CreateNavigator();

            iter = xNav.Select("//w:ls", Namespace);
            this._listings = new List<LsCommand>();
            while (iter.MoveNext())
            {
                LsCommand cmd = new LsCommand();
                cmd.Destination = iter.Current.SelectSingleNode("w:destination/@value", Namespace).Value;

                XPathNodeIterator files = iter.Current
                    .SelectSingleNode("w:files", Namespace)
                    .Select("//w:file", Namespace);

                foreach (XPathNavigator file in files)
                    cmd.Contents.Add(new FileSystemObject(file, Namespace));

                this._listings.Add(cmd);
            }

            iter = xNav.Select("//w:call", Namespace);
            this._calls = new List<CallCommand>();
            while (iter.MoveNext())
            {
                CallCommand cmd = new CallCommand();
                cmd.Command = iter.Current.SelectSingleNode("w:command/@value", Namespace).Value;
                cmd.WorkingFolder = iter.Current.SelectSingleNode("w:destination/@value", Namespace).Value;
                cmd.Output = iter.Current.SelectSingleNode("w:output/@value", Namespace).Value;

                if (iter.Current.SelectSingleNode("w:erroroutput/@value", Namespace) != null)
                    cmd.ErrorOutput = iter.Current.SelectSingleNode("w:erroroutput/@value", Namespace).Value;

                this._calls.Add(cmd);
            }
        }

        public bool IsClosed
        {
            get
            {
                if (!File.Exists(this._logPath))
                    return false;

                FileStream logFs = new FileStream(this._logPath,
                    FileMode.Open,
                    FileAccess.Read,
                    FileShare.ReadWrite,
                    BUFFER_SIZE,
                    FileOptions.Asynchronous);

                StreamReader logRdr = new StreamReader(logFs);
                string logText = logRdr.ReadToEnd();

                return logText.Contains("</session>");
            }
        }

        public Dictionary<string, long> GetDirectoryUsage(string callResult)
        {
            Dictionary<string, long> retVal = new Dictionary<string, long>();
            foreach (string du in callResult.Split((char)10, (char)13))
            {
                if (String.IsNullOrWhiteSpace(du))
                    continue;

                retVal.Add(du.Split('\t')[1], long.Parse(du.Split('\t')[0]));
            }

            return retVal;
        }

        public List<int> GetProcessList(string callResult)
        {
            List<int> retVal = new List<int>();
            foreach (string ps in callResult.Split((char)10, (char)13))
            {
                if (String.IsNullOrWhiteSpace(ps) || ps.IndexOf("-f") < 0)
                    continue;

                string serverCall = ps.ToLower().Remove(0, ps.IndexOf("-f"));
                serverCall = serverCall.Remove(serverCall.IndexOf("-r"));

                int psName;
                if (int.TryParse(serverCall.Split('/')[3].Trim(), out psName))
                    retVal.Add(psName);
            }

            return retVal;
        }
        
        public string LogPath
        {
            get { return this._logPath; }
            set { this._logPath = value; }
        }
        private string _logPath;

        public XmlNamespaceManager Namespace
        {
            get
            {
                if (this._namespace == null)
                {
                    this._namespace = new XmlNamespaceManager(new NameTable());
                    this._namespace.AddNamespace("w", "http://winscp.net/schema/session/1.0");
                }

                return this._namespace;
            }
        }
        private XmlNamespaceManager _namespace;

        private List<LsCommand> _listings;
        public List<LsCommand> Listings
        {
            get { return _listings; }
            set { _listings = value; }
        }

        private List<CallCommand> _calls;
        public List<CallCommand> Calls
        {
            get { return _calls; }
            set { _calls = value; }
        }

        private List<ChModCommand> _chMods;
        private List<MkDirCommand> _mkDirs;
        private List<DownloadCommand> _gets;
        private List<MvCommand> _moves;
        private List<RmCommand> _deletes;
        private List<UploadCommand> _puts;
        private List<TouchCommand> _touches;
    }

    public struct CallCommand
    {
        public string Command
        {
            get { return _command; }
            set { _command = value; }
        }
        private string _command;

        public string WorkingFolder
        {
            get { return _workingFolder; }
            set { _workingFolder = value; }
        }
        private string _workingFolder;

        public string Output
        {
            get { return _output; }
            set { _output = value; }
        }
        private string _output;

        public string ErrorOutput
        {
            get { return _errorOutput; }
            set { _errorOutput = value; }
        }
        private string _errorOutput;

        public override string ToString()
        {
            return "call";
        }
    }

    public struct ChModCommand
    {
        private string _filename;
        private string _permissions;

        public override string ToString()
        {
            return "chmod";
        }
    }

    public struct LsCommand
    {
        public string Destination
        {
            get { return _destination; }
            set { _destination = value; }
        }
        private string _destination;
        
        public List<FileSystemObject> Contents
        {
            get 
            {
                if (this._contents == null)
                    this._contents = new List<FileSystemObject>();

                return _contents; 
            }
            set { _contents = value; }
        }
        private List<FileSystemObject> _contents;

        public override string ToString()
        {
            return "ls";
        }
    }

    public struct MkDirCommand
    {
        private string _folderName;

        public override string ToString()
        {
            return "mkdir";
        }
    }

    public struct DownloadCommand
    {
        public string Filename
        {
            get { return _filename; }
            set { _filename = value; }
        }
        private string _filename;

        public string Destination
        {
            get { return _destination; }
            set { _destination = value; }
        }
        private string _destination;

        public override string ToString()
        {
            return "download";
        }
    }

    public struct MvCommand
    {
        private string _filename;
        private string _destination;

        public override string ToString()
        {
            return "mv";
        }
    }

    public struct RmCommand
    {
        public override string ToString()
        {
            return "rm";
        }
    }

    public struct UploadCommand
    {
        private string _filename;
        private string _destination;

        public override string ToString()
        {
            return "upload";
        }
    }

    public struct TouchCommand
    {
        private string _filename;
        private DateTime _modifyStamp;

        public override string ToString()
        {
            return "touch";
        }
    }
}
